package com.dq.service.messsge.impl;

import com.dq.context.Constant;
import com.dq.exception.customException.AdminException;
import com.dq.domain.subscribe.Subscribe;
import com.dq.domain.message.oneself.Message;
import com.dq.domain.message.oneself.NoticeMessage;
import com.dq.domain.message.oneself.SubscribeMessage;
import com.dq.domain.sys.user.user.base.SysUser;
import com.dq.redis.jedispool.JedisUtils;
import com.dq.repository.BaseRepositories;
import com.dq.service.messsge.SubscribeMessageService;
import com.dq.service.subscribe.SubscribeService;
import com.dq.service.sys.user.SysUserService;
import org.apache.commons.lang3.StringUtils;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * @author cf
 *
 */
@Service
public class SubscribeMessageServiceImpl extends BaseRepositories implements SubscribeMessageService {
    private static Logger log = LoggerFactory.getLogger(SubscribeMessageServiceImpl.class);

    @Autowired
    private SysUserService sysUserService;
    @Autowired
    private JedisUtils jedisUtils;
    @Autowired
    private SubscribeService subscribeService;
    @Autowired
    private Redisson redisson;

    /**
     * 添加
     * @param subscribeMessage
     * @return
     * @throws AdminException
     */
    @Override
    public SubscribeMessage save(SubscribeMessage subscribeMessage, SubscribeMessage.UserType userType, String content)  throws AdminException {
        if(subscribeMessage.getCaseUser() == null) {
            throw new AdminException("请选择消息接收者");
        }
        if(userType == null) {
            throw new AdminException("请选择消息接收者类型");
        }
        subscribeMessage.setUserType(userType);

        subscribeMessage.setMessageStatus(NoticeMessage.MessageStatus.noSee);

        if(userType.name.equals(SubscribeMessage.UserType.staff.name)) {
            subscribeMessage.setMessageType(Message.MessageType.subscribe);
        }else {
            subscribeMessage.setMessageType(Message.MessageType.wxSubscribe);
        }


        subscribeMessage.setVersion(Constant.VERSION_VALUE);
        subscribeMessage.setDeleted(false);
        subscribeMessage.setCreateTime(System.currentTimeMillis());
        subscribeMessage.setLastModifyTime(System.currentTimeMillis());

        try {
            SysUser user = shiroService.getUser();
            if(user != null) {
                subscribeMessage.setCreateUser(user);
                subscribeMessage.setUpdateUser(user);
            }
        }catch (Exception e) {
            e.printStackTrace();
        }

        subscribeMessage.setContent(content);
        subscribeMessage.setOrg(subscribeMessage.getCaseUser().getSection());
        if(StringUtils.isBlank(subscribeMessage.getTheme())) {
            subscribeMessage.setTheme(subscribeMessage.getCaseUser().getUserRealcName());
        }

        dao.save(subscribeMessage);

        return subscribeMessage;
    }

    /**
     * 修改
     * @param subscribeMessage
     * @return
     * @throws AdminException
     */
    @Override
    public SubscribeMessage update(SubscribeMessage subscribeMessage) throws AdminException {
        subscribeMessage.setLastModifyTime(System.currentTimeMillis());

        SysUser user = sysUserService.getUser();
        if(user != null) {
            subscribeMessage.setUpdateUser(user);
        }

        dao.saveOrModify(subscribeMessage);

        return subscribeMessage;
    }

    /**
     * 预约创建，
     * 设置 redis 失效key, 开启预约延时消息提醒
     * @param subscribe 预约实体
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void subscribeRedisBegin(Subscribe subscribe) {
        Long subscribeTime = subscribe.getBeginTime();
        Long nowTime = System.currentTimeMillis();
        if(subscribeTime.compareTo(nowTime) < 0) {
            log.error(" ----> ERROR <---- [预约延时消息提醒 - 001, 设置失效key]: 预约时间已过, 不符合执行延时消息逻辑 --------------------------- ");
        }else {
            // 转成毫秒, 延时消息提醒起点时间
            long beginHm = SUBSCRIBE_LOSE_TIME_BEGIN * 1000;
            log.info(" ----> [预约延时消息提醒 - 001, 设置失效key]: 准备完成: subscribeTime: {},  nowTime: {}, beginHm: {}", subscribeTime, nowTime, beginHm);
            if((subscribeTime-nowTime) > beginHm) {
                long expire = (subscribeTime - beginHm - nowTime)/1000;
                jedisUtils.setString(SUBSCRIBE_LOSE_KEY_PRE + subscribe.getId() + UNDERLINE + 1, 1, (int) expire);
                log.info(" ----> [预约延时消息提醒 - 001, 设置失效key]: 开启延时任务 [>] ， 预约单id: {}， 命中率： {}, 失效时间 {}s", subscribe.getId(), 1, expire);
            }else if((subscribeTime-nowTime) == beginHm) {
                jedisUtils.setString(SUBSCRIBE_LOSE_KEY_PRE + subscribe.getId() + UNDERLINE + 2, 1, 1);
                log.info(" ----> [预约延时消息提醒 - 001, 设置失效key]: 开启延时任务 [=] ， 预约单id: {}， 命中率： {}, 失效时间 {}s", subscribe.getId(), 2, 1);
            }else {
                Integer count = null;
                Long expire = null;
                List<Long> list = new ArrayList<>();
                long begin = subscribeTime - beginHm;
                list.add(begin);
                for (int i = 0; i <= 10; i++) {
                    long gapTime = SUBSCRIBE_LOSE_TIME * 1000;
                    begin = begin + gapTime;
                    if(begin > subscribeTime)  {
                        break;
                    }
                    list.add(begin);
                }
                Collections.sort(list);
                for (int i = 1; i < list.size(); i++) {
                    Long aLong = list.get(i-1);
                    Long bLong = list.get(i);
                    if(nowTime >= aLong && nowTime < bLong) {
                        count = i+1;
                        expire = (bLong - nowTime)/1000;
                        break;
                    }
                }
                if(count == null) {
                    log.error(" ----> ERROR <---- [预约延时消息提醒 - 001, 设置失效key]: 获取时间间隔异常, 开启延时任务 [<]异常 --------------------------- ");
                }else {
                    jedisUtils.setString(SUBSCRIBE_LOSE_KEY_PRE + subscribe.getId() + UNDERLINE + count, 1, expire.intValue());
                    log.info(" ----> [预约延时消息提醒 - 001, 设置失效key]: 开启延时任务 [<] ， 预约单id: {}， 命中率： {}, 失效时间 {}s", subscribe.getId(), count, expire.intValue());
                }
            }
        }
    }
    /**
     * 接受设置的 预约失效key
     * 进行预约延时消息推送逻辑
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void subscribeRedis(String messageKey) {
        if(messageKey.contains(SUBSCRIBE_LOSE_KEY_PRE)) {
            // 获取到锁对象
            RLock rLock = redisson.getLock(messageKey);
            try {
                rLock.lock();

                // 避免失效key 重复消费
                String keyLock = messageKey + "lock";
                String valueLock = "no";
                if(jedisUtils.getString(keyLock) != null && valueLock.equals(jedisUtils.getString(keyLock))) {
                    jedisUtils.delete(keyLock);
                    return;
                }
                jedisUtils.setString(keyLock, valueLock);

                log.info(" ----> [预约延时消息提醒 - 002, 执行发送消息]: 准备完成 ");
                try {
                    String orderNo = messageKey.split("_")[1];
                    int count = Integer.parseInt(messageKey.split("_")[2]);

                    Subscribe subscribe = dao.get(Subscribe.class, Long.parseLong(orderNo));
                    if(subscribe == null) {
                        log.error(" ----> ERROR <---- [预约延时消息提醒 - 002, 执行发送消息]: 无对应预约实体记录 --------------------------- ");
                        return;
                    }

                    String statusIf = Subscribe.SubscribeStatus.confirmed.name;
                    if(!subscribe.getSubscribeStatus().name.equals(statusIf)) {
                        log.error(" ----> ERROR <---- [预约延时消息提醒 - 002, 执行发送消息]: 此预约单状态为:[{} != {}], 不发送延时消息, 预约单id: {} --------------------------- ",
                                subscribe.getSubscribeStatus().name, statusIf, subscribe.getId());
                        return;
                    }

                    if(count >= SUBSCRIBE_LOSE_COUNT) {
                        // 取消 预约单， 给用户发送取消消息
                        subscribeService.cancelSubscribe(subscribe, "预约时间过期， 自动取消预约");
                        log.info(" ----> [预约延时消息提醒 - 002, 执行发送消息]:  延时消息提醒发送成功, 预约时间过期, 取消 预约单， 给用户发送取消消息");
                    }else {
                        int newCount = count + 1;
                        int expire = SUBSCRIBE_LOSE_TIME.intValue();

                        Long subscribeTime = subscribe.getBeginTime();
                        Long nowTime = System.currentTimeMillis();
                        // 预约时间 与当前时间间隔
                        long time = (SUBSCRIBE_LOSE_COUNT - newCount) * SUBSCRIBE_LOSE_TIME / 60;

                        String msg = "";
                        if(time > 0) {
                            msg = "预约单还有" + time + "分钟, 到预约时间, 请尽快到门店";
                            expire = SUBSCRIBE_LOSE_TIME.intValue();
                        }else {
                            msg = "预约单已到预约时间," + SUBSCRIBE_LOSE_TIME_RETAIN/60 + " 分钟后会自动取消";
                            expire = SUBSCRIBE_LOSE_TIME_RETAIN.intValue();
                        }
                        // 再次设置失效key
                        jedisUtils.setString(SUBSCRIBE_LOSE_KEY_PRE + orderNo + UNDERLINE + newCount, newCount, expire);

                        // 给用户返送消息
                        subscribeService.subscribeMessage(subscribe, subscribe.getWxUser(), SubscribeMessage.UserType.client, msg);
                        log.info(" ----> [预约延时消息提醒 - 002, 执行发送消息]: 延时消息提醒发送成功; 预约时间:{}-现时:{}; 预约单id: {}, 命中率： {}, 失效时间 {}s， msg: {}", subscribeTime, nowTime, subscribe.getId(), newCount, expire, msg);
                    }
                }catch (Exception e) {
                    e.printStackTrace();
                    log.error(" ----> ERROR <---- [预约延时消息提醒 - 002, 执行发送消息]: 延时消息发送逻辑异常, msg:{}", e.getMessage());
                }

                /// 放到前面,确定失效key只执行一次
//                jedisUtils.setString(keyLock, valueLock);
            }catch (Exception e){
                e.printStackTrace();
                log.error(" ----> ERROR <---- [预约延时消息提醒 - 002, 执行发送消息]:  -- redis 加锁逻辑, msg: " + e.getMessage());
            } finally {
                if (rLock.isLocked())
                    if (rLock.isHeldByCurrentThread())
                        rLock.unlock();
            }
        }
    }

    /**
     * -
     */
    public static final String UNDERLINE = "_";
    /**
     * 预约单, 设置失效 间隔, 秒 []
     */
    public static final Long SUBSCRIBE_LOSE_TIME = 1*60L;
    /**
     * 预约单, 设置失效 起始, 秒
     */
    public static final Long SUBSCRIBE_LOSE_TIME_BEGIN = 3*60L;
    /**
     * 预约单, 设置失效 保留时间, 秒
     */
    public static final Long SUBSCRIBE_LOSE_TIME_RETAIN = 1*60L;
    /**
     * 预约单, 设置失效前缀 key
     */
    public static final String SUBSCRIBE_LOSE_KEY_PRE = "subscribeLosePre_";
    /**
     * 预约单, 设置失效命中次数 key
     */
    public static final String SUBSCRIBE_LOSE_KEY_COUNT_PRE = "subscribeLoseCountPre_";
    /**
     * 预约单, 命中数最大值 [].size + 2
     */
    public static final Long SUBSCRIBE_LOSE_COUNT = (SUBSCRIBE_LOSE_TIME_BEGIN / SUBSCRIBE_LOSE_TIME) + 2;

}

